/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ClassAlgebra;

import HierarchyPackage.HClass;
import HierarchyPackage.Hierarchy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author Martin Haulrich
 */
public class ClassAlgebraSingleton {

    final static int DIFFERENCE = 1;

    final static int UNION = 2;

    final static int INTERSECTION = 3;

    Map<Expression, Expression> cache;

    final Expression empty;

    protected ClassAlgebraSingleton() {
        empty = new EmptyExpression();
        cache = new LinkedHashMap<Expression, Expression>(12800, 0.75F, true) {

            @Override
            protected boolean removeEldestEntry(Map.Entry<Expression, Expression> eldest) {
                return size() > 10000;
            }
        };

    }

    private static class CAHolder {

        private static final ClassAlgebraSingleton INSTANCE = new ClassAlgebraSingleton();

    }

    public static ClassAlgebraSingleton getInstance() {
        return CAHolder.INSTANCE;
    }

    /**
     * Map an expression without having access to the hierarchy the classes are from.
     * This allows for much less reducing because the superclassOf and getIntersection methods are not available.
     * @param e
     * @return
     */
    public Expression map(Expression e) {

        return map(e, null);
    }

    /**
     * Map an expression in order to try and reduce it.
     * Uses the superclassOf and getIntersection from the Hierarchy to reduce intersection expression further
     * @param e
     * @param hier
     * @return
     */
    public Expression map(Expression e, Hierarchy hier) {

//        System.err.println("Map: " + e);
        if (e instanceof EmptyExpression) {
            return empty;
        }
        Expression ee = cache.get(e);
        //Expression ee = null;

//        System.out.println("map");
//        if (ee != null) {
//            System.out.println("    hit");
//        }
//        else {
        if (ee == null) {
//            System.err.println("switch " + e);
//            System.err.println(e.type());
            switch (e.type()) {

                // Reductions/mappings for complex expression
                // if (e.type() == Expression.COMPLEX) {
                case Expression.COMPLEX:
                    //      System.err.println("COM");
                    ComplexExpression ce = (ComplexExpression) e;
                    //      System.err.println(ce.operation);
                    switch (ce.operation) {
                        case DIFFERENCE:
                            ee = mapDifference(ce, hier);
                            break;
                        case UNION:
                            ee = mapUnion(ce, hier);
                            break;
                        case INTERSECTION:
                            ee = mapIntersection(ce, hier);
                            break;
                        default:
                            System.err.println("Error. Invalid operation");
                            ee = null;
                    }
                    break;

                // Reduction/mappings for list expression
                //if (e.type() == Expression.LIST) {
                case Expression.LIST:
                    ListExpression le = (ListExpression) e;

                    // If the list does not contains any elements return the empty set
                    if (le.expList.isEmpty()) {
                        ee = empty;
                    } else {


                        // If the list consists of one elements simply return this element.
                        if (le.expList.size() == 1) {
                            // This is not nice - consider alternative solution
                            ee = le.expList.iterator().next();
                        } else {

                            // Run through list and map all epxression in the list, creating a new list with the results of these mappings
                            List<Expression> newList = new ArrayList<Expression>();
                            for (Expression exp : le.expList) {
                                newList.add(map(exp, hier));
                            }

                            /**
                             * @TODO Intersection, list-pruning
                             */
                            // Pruning a union-list
                            if (le.operation == ListExpression.UNION) {

                                // A list is used instead of a set, because there can be more than one occurence of an expression that should be removed.
                                List<Expression> delete = new ArrayList<Expression>();

                                // Run through elements and for each elements run through the following in the list and check if
                                // reductions can be made. I the "outer" element is removed do not remove from the list because this will
                                // cause problems in the iteration. Instead add it to a list of elements to be removed after the iteration.
                                for (int i = 0; i < newList.size(); i++) {
                                    Expression a = newList.get(i);
                                    // Flag to tellif  the current a element been deleted
                                    boolean deleted = false;
                                    // The element is the empty set. Remove it.
                                    if (a instanceof EmptyExpression) {
                                        delete.add(a);
                                        deleted = true;
                                    }

                                    // Here I use an iterator in order to remove element from the list
                                    for (ListIterator<Expression> it = newList.listIterator(i + 1); it.hasNext() && !deleted;) {
                                        Expression b = it.next();

                                        // The two elements are the same. Remove the first one;
                                        if (a.equals(b)) {
                                            delete.add(a);
                                            deleted = true;
                                        }

                                        // If the two expression are simple and we have a hierachy we can use this to prune furhter
                                        if ((a.type() == Expression.SIMPLE) && (b.type() == Expression.SIMPLE) && (hier != null)) {
                                            HClass ca = ((SimpleExpression) a).c;
                                            HClass cb = ((SimpleExpression) b).c;
                                            if (hier.superclassOf(cb, ca)) {
                                                delete.add(a);
                                                deleted = true;
                                            }
                                            if (hier.superclassOf(ca, cb)) {
                                                it.remove();
                                            }
                                        }
                                    }
                                }

                                // I do not use removeAll because I do not know if it will remove multiple occurences
                                for (Expression exp : delete) {
                                    newList.remove(exp);
                                }
                                ee = new ListExpression(UNION, new HashSet<Expression>(newList));
                            }
                        }
                    }
                    break;
                default:
                    ee = e;
            }
            cache.put(e, ee);
        }
        return ee;
    }

    /**
     * Mapping of a complex expression with the difference-operator
     * @param e
     * @param hier
     * @return
     */
    private Expression mapDifference(ComplexExpression e, Hierarchy hier) {

//        System.err.println("MapDifference: " + e);

        if (e.e1.equals(e.e2)) {
            return empty;
        }

        // Subtracting something from an empty set gives us an empty set.
        if (e.e1 instanceof EmptyExpression) {

            return empty;
        }

        // If e2 is a superclass to e1 the result of the set-difference operation is the empty set
        if ((e.e1.type() == Expression.SIMPLE) && (e.e2.type() == Expression.SIMPLE)) {
            if (hier != null) {
                if (hier.superclassOf(((SimpleExpression) e.e2).c, ((SimpleExpression) e.e1).c)) {
                    return empty;
                }
            }
        }

        return new ComplexExpression(DIFFERENCE, map(e.e1, hier), map(e.e2, hier));
    }

    /**
     * Mapping of a complex expression with the union-operator
     * @param e
     * @param hier
     * @return
     *
     */
    private Expression mapUnion(ComplexExpression e, Hierarchy hier) {

//        System.err.println("mapUnion: " + e);

        if (e.e1.equals(e.e2)) {
            return e.e1;
        }

//        // In case one expression is a union list just add the other to this.
//        boolean go = false;
//        ListExpression list = null;
//        Expression other = null;
//        if (e.e2.type() == Expression.LIST) {
//            list = (ListExpression) e.e2;
//            other = e.e1;
//            go = true;
//        }
//        if (e.e1.type() == Expression.LIST) {
//            list = (ListExpression) e.e1;
//            other = e.e2;
//            go = true;
//        }
//        if (go) {
//            if (list.operation == ListExpression.UNION) {
//                List<Expression> newList = new ArrayList<Expression>(list.expList);
//                newList.add(map(other,hier));
//                return map(new ListExpression(ListExpression.UNION, newList),hier);
//            }
//        }

        // If e2 is a superclass to e1 then return e2, if e1 is a superclass to e2 then return e1
        if ((e.e1.type() == Expression.SIMPLE) && (e.e2.type() == Expression.SIMPLE)) {
            if (hier != null) {
                if (hier.superclassOf(((SimpleExpression) e.e2).c, ((SimpleExpression) e.e1).c)) {
                    return e.e2;
                }
                if (hier.superclassOf(((SimpleExpression) e.e1).c, ((SimpleExpression) e.e2).c)) {
                    return e.e1;
                }
            }
        }

        return new ComplexExpression(UNION, map(e.e1, hier), map(e.e2, hier));
    }

    /**
     * Mapping of a complex expression with the intersection-operator
     * @param e
     * @param hier
     * @return
     *
     */
    private Expression mapIntersection(ComplexExpression e, Hierarchy hier) {


//        System.err.println("MapIntersection: " + e);
        //      System.err.println("MapInterxection in: " + e);
        if (e.e1.equals(e.e2)) {
            return e.e1;
        }

        if (e.e1.type() == Expression.EMPTY || e.e2.type() == Expression.EMPTY) {
            return empty;
        }

//        // In case one expression is a intersection list just add the other to this.
//        boolean go = false;
//        ListExpression list = null;
//        Expression other = null;
//        if (e.e2.type() == Expression.LIST) {
//            list = (ListExpression) e.e2;
//            other = e.e1;
//            go = true;
//        }
//        if (e.e1.type() == Expression.LIST) {
//            list = (ListExpression) e.e1;
//            other = e.e2;
//            go = true;
//        }
//        if (go) {
//            if (list.operation == ListExpression.INTERSECTION) {
//                List<Expression> newList = new ArrayList<Expression>(list.expList);
//                newList.add(map(other,hier));
//                return map(new ListExpression(ListExpression.INTERSECTION, newList),hier);
//            }
//        }
//

// If both e1 and e2 are simple and there is a hierarchy available this can be used for further reductions
        if ((e.e1.type() == Expression.SIMPLE) && (e.e2.type() == Expression.SIMPLE)) {
            if (hier != null) {
                // If e2 is a superclass to e1 then return e1, if e1 is a superclass to e2 then return e2
                //              System.err.println("Intersect with hier: " + e + "   " + hier);
                if (hier.superclassOf(((SimpleExpression) e.e2).c, ((SimpleExpression) e.e1).c)) {
                    return e.e1;
                }

                if (hier.superclassOf(((SimpleExpression) e.e1).c, ((SimpleExpression) e.e2).c)) {
                    return e.e2;
                }

//              System.err.println("No one is super");
// Get the intersection of the two classes using the hierarchy
                Set<HClass> intersect = hier.getIntersection(((SimpleExpression) e.e1).c, ((SimpleExpression) e.e2).c);
                //              System.err.println("Intersect is: " + intersect);
                if (intersect.isEmpty()) {
                    return empty;
                }

                if (intersect.size() == 1) {
                    //                 System.err.println("One left...");
                    return new SimpleExpression(intersect.iterator().next());

                }
// If the intersection of the two classes are the classes themselves, i.e. the size of the
// intersection is 2 there os no reduction to me made

            }
        }

        ComplexExpression a = null;

        Expression b = null;

        boolean todoComplex = false;

        boolean todoList = false;

        // At least one of the expression has to be complex for there to be anything to do
        // Make 'a' that expression.
        if (e.e1.type() == Expression.COMPLEX) {
            a = (ComplexExpression) e.e1;
            b =
                    e.e2;
            todoComplex =
                    true;
        } else {
            if (e.e2.type() == Expression.COMPLEX) {
                a = (ComplexExpression) e.e2;
                b =
                        e.e1;
                todoComplex =
                        true;
            }

        }

        if (todoComplex) {

            // a ∩ (b - c) = (a ∩ b) - (a ∩ c)
            if (a.operation == DIFFERENCE) {
                // Try to map instantly in order to use disjunctive hierarchies to reduce the expression
                Expression newE1 = map(new ComplexExpression(INTERSECTION, a.e1, b), hier);
                // Try to map instantly in order to use disjunctive hierarchies to reduce the expression
                Expression newE2 = map(new ComplexExpression(INTERSECTION, a.e2, b), hier);
                return map(new ComplexExpression(DIFFERENCE, map(newE1, hier), map(newE2, hier)), hier);
            }
// a ∩ (b ∪ c) = (a ∩ b) ∪ (a ∩ c)

            if (a.operation == UNION) {
                // Try to map instantly in order to use disjunctive hierarchies to reduce the expression
                Expression newE1 = map(new ComplexExpression(INTERSECTION, a.e1, b), hier);
                // Try to map instantly in order to use disjunctive hierarchies to reduce the expression
                Expression newE2 = map(new ComplexExpression(INTERSECTION, a.e2, b), hier);
                return map(new ComplexExpression(UNION, map(newE1, hier), map(newE2, hier)), hier);
            }

        }

        // If one of the expression is a list expression make c that expression
        ListExpression c = null;

        if (e.e1.type() == Expression.LIST) {
            c = (ListExpression) e.e1;
            b =
                    e.e2;
            todoList =
                    true;
        } else {
            if (e.e2.type() == Expression.LIST) {
                c = (ListExpression) e.e2;
                b =
                        e.e1;
                todoList =
                        true;
            }

        }

        if (todoList) {

            // a ∩ ⋃b = ⋃a∩b
            if (c.operation == UNION) {
                Set<Expression> newList = new HashSet<Expression>();
                for (Expression cl : c.expList) {
                    newList.add(map(new ComplexExpression(INTERSECTION, map(b, hier), map(cl, hier)), hier));
                }

                return map(new ListExpression(UNION, newList), hier);
            }

        }
        Expression res = map(new ComplexExpression(INTERSECTION, map(e.e1, hier), map(e.e2, hier)), hier);
        //    System.err.println("MapInterxection out: " + res);
        return res;
    }
}
